package com.stone.encrypt.aspect;

import com.stone.encrypt.annotation.Decrypt;
import com.stone.encrypt.app.service.EncryptService;
import com.stone.encrypt.infra.util.EncryptionUtils;
import com.stone.starter.core.constant.BaseConstants;
import com.stone.starter.core.exception.EncryptionException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 解密切面
 *
 * @author Mr_wenpan@163.com 2021/08/02 21:39
 */
@Slf4j
@Aspect
@Component
public class DecryptAspectHandler implements ApplicationContextAware {

    /**
     * 用于SpEL表达式解析.
     */
    private static final SpelExpressionParser PARSER = new SpelExpressionParser();
    /**
     * 解密解密服务类
     */
    private EncryptService encryptService;

    @Pointcut("@annotation(com.stone.encrypt.annotation.Decrypt)")
    public void decrypt() {
    }

    @SneakyThrows
    @AfterReturning(value = "decrypt()", returning = "returnValue")
    public void afterReturning(JoinPoint joinPoint, Object returnValue) {
        handleDecrypt(joinPoint, returnValue);
    }

    /**
     * 处理解密
     *
     * @param joinPoint   连接点
     * @param returnValue 方法返回值
     * @author Mr_wenpan@163.com 2021/8/3 11:07 下午
     */
    private void handleDecrypt(JoinPoint joinPoint, Object returnValue) {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Decrypt annotation = method.getAnnotation(Decrypt.class);
        String[] decryptFields = annotation.decryptFields();
        String returnName = decryptFields[0].split("\\.")[0];

        if (ArrayUtils.isEmpty(decryptFields)) {
            return;
        }

        // 处理待解密的字段
        decryptFields = handleDecryptFields(decryptFields);
        // 解密
        handleDecrypt(returnValue, decryptFields, annotation, returnName);
    }

    /**
     * 解密方法返回值
     */
    private void handleDecrypt(Object returnValue, String[] decryptFields, Decrypt annotation, String returnName) {

        EvaluationContext context = new StandardEvaluationContext();
        // 1、如果返回值是list集合类型
        if (returnValue instanceof List) {
            List list = (List) returnValue;
            if (CollectionUtils.isEmpty(list)) {
                return;
            }
            // 遍历处理集合中的每个元素
            for (Object item : list) {
                // 将返回值设置到上下文中
                context.setVariable(returnName, item);
                executeDecrypt(decryptFields, context, annotation);
            }
            return;
        }

        // 2、如果返回值是map集合类型
        if (returnValue instanceof Map) {
            // todo 待实现
            return;
        }

        // 3、返回值是对象类型，将返回值设置到上下文中
        context.setVariable(returnName, returnValue);
        executeDecrypt(decryptFields, context, annotation);
    }

    /**
     * 执行解密
     *
     * @param decryptFields 需要解密的字段
     * @param context       解密上下文
     * @param annotation    解密注解
     * @author Mr_wenpan@163.com 2021/8/4 11:40 上午
     */
    private void executeDecrypt(String[] decryptFields, EvaluationContext context, Decrypt annotation) {
        // 遍历每一个需要解密的对象属性
        for (String field : decryptFields) {
            // 解析过后的Spring表达式对象
            Expression expression = PARSER.parseExpression(field);
            // 通过解析表达式在context中获取值
            Object value = expression.getValue(context);
            if (!(value instanceof String)) {
                throw new EncryptionException("解密字段的类型必须为String，请检查解密字段的类型！");
            }
            // 表达式从上下文中计算出实际参数值
            String expressionValue = expression.getValue(context, String.class);
            // 空值默认不解密
            if (Objects.nonNull(expressionValue)) {
                String prefix = annotation.prefix();
                // 解密前先去掉前缀
                if (StringUtils.isNotBlank(prefix)) {
                    expressionValue = trimString(expressionValue, prefix, Boolean.TRUE);
                }
                String decryptResult = executeDecrypt(expressionValue, annotation);
                // 设置回原属性
                PARSER.parseExpression(field).setValue(context, decryptResult);
                continue;
            }
            log.warn("传递的解密字段解析表达式【 {} 】为空，请检查需要解密的字段是否设置正确.", field);
        }
    }

    /**
     * 执行解密
     *
     * @param fieldValue 解密的属性值
     * @param annotation 解密注解
     * @return java.lang.String
     * @author Mr_wenpan@163.com 2021/8/3 11:06 下午
     */
    private String executeDecrypt(String fieldValue, Decrypt annotation) {
        String result = null;
        switch (annotation.decryptType()) {
            case AES:
                // securityKey接口用户没有实现，则无法进行RSA解密（AES解密必须要securityKey）
                if (Objects.isNull(encryptService)) {
                    throw new EncryptionException("解密异常，因为无法获取到securityKey，无法使用AES解密，请检查securityKey获取接口实现.");
                }
                result = EncryptionUtils.AES.decrypt(fieldValue, encryptService.getSecurityKey());
                break;
            case RSA:
                // 公钥接口用户没有实现，则无法进行RSA解密（RSA解密必须要公钥）
                if (Objects.isNull(encryptService)) {
                    throw new EncryptionException("解密异常，因为无法获取到公钥，无法使用RSA解密，请检查公钥获取接口实现.");
                }
                // 获取解密公钥（私钥解密暂不实现）
                String publicKey = encryptService.getPublicKey();
                String salt = annotation.salt();
                if (annotation.withSalt()) {
                    // 公钥加盐解密（对称解密）
                    result = EncryptionUtils.RSA.decrypt(fieldValue, salt, publicKey);
                } else {
                    // 公钥不加盐解密（对称解密）
                    result = EncryptionUtils.RSA.decrypt(fieldValue, publicKey);
                }
                break;
            case MD5:
                throw new EncryptionException("抱歉！为了安全建议，不提供MD5加密算法的解密实现!");
            case RSA2:
                // 暂时不实现
                throw new EncryptionException("抱歉！RSA2解密算法暂未实现，请更换其他算法进行解密.");
            default:
                break;
        }
        return result;
    }

    /**
     * <h5>功能:去除字符串开头结尾的指定字符串</h5>
     *
     * @param str    要处理的字符串
     * @param delStr 指定要去除的字符串
     */
    public static String trimString(String str, String delStr, boolean removeBegin) {
        int delStrLength = delStr.length();
        if (removeBegin) {
            if (str.startsWith(delStr)) {
                str = str.substring(delStrLength);
            }
            return str;
        }
        if (str.endsWith(delStr)) {
            str = str.substring(0, str.length() - delStrLength);
        }
        return str;
    }

    /**
     * 处理待解密的字段
     */
    private String[] handleDecryptFields(String[] decryptFields) {
        // 处理待解密的字段
        String[] result = new String[decryptFields.length];
        for (int i = 0; i < decryptFields.length; i++) {
            if (!decryptFields[i].startsWith(BaseConstants.Symbol.WELL)) {
                result[i] = BaseConstants.Symbol.WELL.concat(decryptFields[i]);
            } else {
                result[i] = decryptFields[i];
            }
        }
        return result;
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        try {
            encryptService = applicationContext.getBean(EncryptService.class);
        } catch (Exception ex) {
            log.warn("没有EncryptService实现类，将无法使用需要获取秘钥 / 公钥的解密方式.");
        }
    }
}
